import string
import random
import hashlib
import requests
from bs4 import BeautifulSoup
from flask import request,redirect
from flask_restful import Resource
import datetime,urllib.parse

class WeixinError(Exception):

    def __init__(self, msg):
        super(WeixinError, self).__init__(msg)

class WeixinPayError(WeixinError):

    def __init__(self, msg):
        super(WeixinPayError, self).__init__(msg)


class weChat(object):

    def __init__(self, appid, appsecret, mch_id, device_info, key, total_fee,scene_info, body, notify_url,redirect_url):
        self.appid = appid
        self.appsecret = appsecret
        self.mch_id = mch_id
        self.device_info = device_info
        self.key = key
        self.total_fee = total_fee
        self.scene_info = scene_info
        self.body = body
        self.notify_url = notify_url
        self.redirect_url = redirect_url

    @staticmethod
    def user_ip_address():
        '''
        :return: 获取IP
        '''
        ip = request.headers['X-Real-IP']
        print('>>>ip>>>>:',ip)
        return ip

    @staticmethod
    def out_trade_no():
        '''
        :return: 生成订单号
        '''
        nowTime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
        randomNum = random.randint(0, 100)
        if randomNum <= 10:
            randomNum = str(0) + str(randomNum)
        uniqueNum = str(nowTime) + str(randomNum)
        return uniqueNum

    def get_openid(self,code):
        '''
        :param code:
        :return: 微信内公众号支付：通过微信授权的code获取openid
        '''
        params = {
            'appid': self.appid,
            'secret': self.appsecret,
            'code': code,
            'grant_type': 'authorization_code'
        }
        url = 'https://api.weixin.qq.com/sns/oauth2/access_token'
        result = requests.get(url,params=params).json()
        # if result['errcode']:
        #     # raise WeixinPayError(result['errmsg'] + result)
        #     raise result
        return result['openid']


    def getNonce_str(self,length=32):
        '''
        :param length:
        :return: 32位随机字符串
        '''
        char = string.ascii_letters + string.digits
        return "".join(random.choice(char) for _ in range(length))

    def getSign(self,data):
        '''
        :param data:
        :return: 根据发送给微信的字段生成签名
        '''
        stringA = ''
        for k in sorted(data.keys()):
            v = data.get(k)
            string = k+'='+v+'&'
            stringA = stringA + string
        stringA = stringA + 'key=A4C486A5F25B1211F8BF48116603DF7E'
        return hashlib.md5(stringA.encode('utf-8')).hexdigest().upper()

    def trans_dict_to_xml(self,data):
        '''
        :param data:
        :return: 把微信需要的字段转换为微信支持的xml格式
        '''
        xml = []
        for k in sorted(data.keys()):
            v = data.get(k)
            if k == 'detail' and not v.startswith('<![CDATA['):
                v = '<![CDATA[{}]]>'.format(v)
            xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
        return '<xml>{}</xml>'.format(''.join(xml)).encode('utf-8')

    def trans_xml_to_dict(self,content):
        '''
        :param content:
        :return: 从微信返回的xml格式信息转化成字典
        '''
        soup = BeautifulSoup(content)
        xml = soup.find('xml')
        if not xml:
            return {}
        # 将 XML 数据转化为 Dict
        data = dict([(item.name, item.text) for item in xml.find_all()])
        return data


    def last_data(self,url,data):

        data.setdefault('appid', self.appid)
        data.setdefault('mch_id', self.mch_id)
        data.setdefault('nonce_str', self.getNonce_str())
        data.setdefault('sign', self.getSign(data))

        xml = self.trans_dict_to_xml(data)
        req = requests.post(url=url, data=xml)
        result = self.trans_xml_to_dict(req.content)
        if result['return_code'] != 'SUCCESS':
            return WeixinPayError(result['return_msg'])
        # red_url = urllib.parse.urlencode(redirect_url)
        # return result['mweb_url']+'&%s'%(red_url)

        return result


    def Place_on_order(self,**data):
        '''
        :param data:
        :return: 微信外h5下单
        '''
        url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'

        data.setdefault('notify_url',self.notify_url)
        data.setdefault('body',self.body)
        data.setdefault('device_info',self.device_info)
        data.setdefault('spbill_create_ip',self.user_ip_address())
        data.setdefault('out_trade_no',self.out_trade_no())
        data.setdefault('total_fee',self.total_fee)
        data.setdefault('trade_type', 'MWEB')   # H5的交易类型
        data.setdefault('scene_info', self.scene_info)

        return self.last_data(url,data)


    def Inside_place_on_order(self, code, **data):
        '''
        :param code:
        :param data:
        :return: 微信内为  公众号支付 jsapi 提供 perpay_id
        '''

        data.setdefault('body', self.body)
        data.setdefault('out_trade_no', self.out_trade_no())
        data.setdefault('total_fee', self.total_fee)
        data.setdefault('spbill_create_ip', self.user_ip_address())
        data.setdefault('notify_url', self.notify_url)
        data.setdefault('trade_type', 'JSAPI')
        data.setdefault('openid', self.get_openid(code))

        url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'

        return self.last_data(url,data)

    def Query_order(self,out_trade_no,**data):
        '''
        :param out_trade_no:
        :param data:
        :return: 传递商户订单号查询订单状态
        '''
        url = 'https://api.mch.weixin.qq.com/pay/orderquery'

        data.setdefault('out_trade_no',out_trade_no)

        return self.last_data(url,data)

    def close_order(self, out_trade_no, **data):
        """
        关闭订单
        out_trade_no必填
        appid, mchid, nonce_str不需要填入
        """
        url = "https://api.mch.weixin.qq.com/pay/closeorder"

        data.setdefault("out_trade_no", out_trade_no)

        return self.last_data(url, data)


    def Refund_example(self,out_trade_no,**data):
        '''
        :param data:
        :return:申请退款
        '''
        url='https://api.mch.weixin.qq.com/secapi/pay/refund'

        data.setdefault('out_trade_no',out_trade_no)
        data.setdefault('out_refund_no',self.out_trade_no())
        data.setdefault('total_fee',)
        data.setdefault('refund_fee',)

        return self.last_data(url,data)



my_pay = weChat(
    appid = 'wxa78e72f0cb14c4cc',  # 公众账号的ID
    appsecret = '4322bf754ed579684e4e2cf1dd1ecf06', #公众账号的唯一的secret
    mch_id = '1490012632',  # 商户号
    device_info = 'WEB',
    key = 'A4C486A5F25B1211F8BF48116603DF7E',  # 微信账号的唯一key
    scene_info = "{'h5_info': {'type': '场景类型','wap_url': 'WAP网站URL地址','ap_name': 'WAP 网站名'}}", # 场景信息s
    body = '微信支付',  # 商品描述(中文会出问题，已解决)
    total_fee = '1',  # 订单金额
    notify_url = 'http://h5.lo.o3.vc/',  # 支付结束跳转的页面url 不能带参数
    redirect_url = 'http://h5.lo.o3.vc/'
)



class WeChatPay(Resource):
    '''
    微信外浏览器支付
    '''
    def get(self):
        return my_pay.Place_on_order()


class WeJsapiPay(Resource):
    '''
    微信内公众号支付,jsapi
    '''
    def get(self):
        code = request.args.get('code')
        if not code:
            return 'code error'
        else:
            return my_pay.Inside_place_on_order(code)


class WeRefund(Resource):
    '''
    微信申请退款接口
    '''
    def get(self):
        return '微信申请退款'


class Wequery(Resource):
    '''
    微信查询账单
    '''

    def get(self):
        out_trade_no = request.args.get('out_trade_no')
        if out_trade_no:
            return my_pay.Query_order(out_trade_no)
        return '没有订单'
